package uk.ac.aber.Blockmation;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

/**
 * This class displays the image provided with setImage() method.
 * If block attribute is set to be different than 1x1 (and default is 15x15) then
 * every coming image is expected to be miniature pixel-size image. This way, each pixel
 * is then scaled up to the dimensions of the block. This gives the user the illusion
 * of watching image being made of sqared blocks rather than little pixels themselves.  
 * 
 * <br />
 * So methods setting and getting the image perform scaling of it first.
 * Look at the specific methods below.
 * @author Aron
 *
 */
public class Display extends JPanel {
	
	/** the width of default displayed image */
    final static int WIDTH = 20;
    
	/** the height of default displayed image */
    final static int HEIGHT = 20;
    
	/** the default block size */
    final static Dimension BLOCK_DEFAULT = new Dimension(15, 15);
    
	/** default background color */
    final static Color BACKGROUND = Color.white;
    
	/** the displayed image */
    private BufferedImage image;
    
	/** the current dimension of block (block size) */
    private Dimension block;
    
	/** AF object to perform scaling */
    private AffineTransform at = new AffineTransform();
    
    private Color backgroundColor;
	
	/**
	 * Constructs Display object with image with height, width and color as
	 * specified by default constants. This JPanel size is set to be size of the  
	 * created image.
	 */
    public Display()
    {
        this.backgroundColor = BACKGROUND;
    	block = BLOCK_DEFAULT;
        setImage( new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB) );
        this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
        clear();
    }
    
   /**
    * Clears the displaying image with background color.
    */   
    public void clear()
    {
        Graphics2D g2;
        g2 = image.createGraphics();
        g2.setColor(backgroundColor);
        g2.fillRect(0, 0, image.getWidth(), image.getHeight());
        g2.drawImage(image, null, 0, 0);
        g2.dispose();
        
        repaint();
    }
	
	/**
	 * Main paint method the component. It simply draws the image onto
	 * its canvas using Graphics2D.drawImage() method.
	 */
    public void paint(Graphics g)
    {
        super.paint(g);

        Graphics2D g2 = (Graphics2D) g;

        if (image != null)
        {
            g2.drawImage(image, null, 0, 0);
        }
    }
	
	/**
	 * Sets the image to be displayed by this Display class.
	 * The width (and height) of coming image is resized by factor
	 * of DEFAULT_BLOCK width (and height) respectively. The block size
	 * can be set by this class setBlockSize() method.
	 *
	 * @param newImage - image to be displayed in its non-resized version
	 */
    public void setImage(BufferedImage newImage)
    {
        // resize up to the block size;
        at.setToScale(block.getWidth(), block.getHeight());

        try
        {
        	image = new BufferedImage(newImage.getWidth() * block.width,
                newImage.getHeight() * block.height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2 = image.createGraphics();
            // draw newImage onto image using at transformation
            g2.drawImage(newImage, at, null);
            g2.dispose();
        } catch (OutOfMemoryError err)
        {
        	System.err.println("Not enough memory for Java Virtual Machine. " +
        			"Allow more memory or refrain from processing. " +        			 
        			"Solution: http://hausheer.osola.com/docs/5");
        	
        }

        this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
        repaint();
        
    }
    
	/**
	 * Gets the image that is currently displayed by this Display class.
	 * If the block size is different that 1/1 (what means the coming picture
	 * has been resized up), then the picture you get is sized down version.
	 * @return
	 */
    public BufferedImage getImage()
    {
        at.setToScale(1/block.getWidth(), 1/block.getHeight());

        BufferedImage imageOut = new BufferedImage(image.getWidth() / block.width,
                image.getHeight() / block.height, BufferedImage.TYPE_INT_RGB);

        Graphics2D g2 = imageOut.createGraphics();
        // draw newImage onto image using at transformation
        g2.drawImage(image, at, null);
        g2.dispose();

        return imageOut;
    }
    
    /**
     * Gets the image that is displayed exactly as it is displayed.
     * No zooming here. 
     * @return displayed image
     */
    public BufferedImage getDisplayedImage()
    {
    	return image;
    }

    /**
     * Sets the size of blocks that the displayed image appears to 
     * be made of. It's used to perform scaling of the coming/outgoing
     * image in the following way: each pixel is scaled by block.width and 
     * block.height respectively. If you require the illusion of squares, 
     * which the assignments asks for, provide width and height of the same
     * value. 
     * @param newSize
     */
    public void setBlockSize(Dimension newSize)
    {
    	/*
    	 * for standard 20x20 pictures, JVM won't allow you to zoom more
    	 * than x195. And the restrictions below will satisfy most users.
    	 */ 
     	if (newSize.width > 0 && newSize.width <= 195 &&
     			newSize.height > 0 && newSize.height <= 195)
     	{
        	BufferedImage original = getImage();
        	
        	this.block = newSize;
        	
        	setImage(original);
        	
            this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
            repaint();
     		
     	} else 
     	{
     		System.err.println("Dimension of blocks must be: > 0, <= 195");
     	}
    }
    
    /**
     * Returns the size of blocks.
     * @return size of blocks
     */
    public Dimension getBlockSize()
    {
    	return this.block;
    }

    /**
     * Gets color of the background, which the clear() method uses.
     * And the derived class DrawingPad too.
     * @return current color of the background
     */
	public Color getBackgroundColor() {
		return backgroundColor;
	}
	
	/**
	 * Sets background color. 
	 * @see getBackgroundColor()
	 * @param background - new background color
	 */
	public void setBackgroundColor(Color background) {
		this.backgroundColor = background;
	}
	
}
